#pragma once

#include <string>
#include <cstdlib>
#include <stdexcept>
#include <memory>
#include <algorithm>
#include <list>
#include <vector>
#include "rpc_singleton.h"

#define USE_POOL 1

namespace rpc {

class _ObjectPoolBase {
public:
    virtual ~_ObjectPoolBase() {}
    // Returns the allocated object's count
    // @return The allocated object's count
    virtual std::size_t getCount() const = 0;
    // Returns std::type_info of 'ObjectType'
    // @return std::type_info of 'ObjectType'
    virtual const std::type_info& getTypeInfo() const = 0;
    // Returns the size in byte of 'ObjectType'
    virtual std::size_t getObjectSize() const = 0;
};

using PoolList = std::list<_ObjectPoolBase*>;

// Object pool manager
class _ObjectPoolManager {    
    PoolList poolList_; // object pool list

public:
    ~_ObjectPoolManager() {
        PoolList temp = poolList_;
        for (auto pool : temp) {
            delete pool;
        }
    }
    // Returns pools
    const PoolList getPools() {
        return poolList_;
    }

private:
    // Adds a pool singleton pointer
    void add(_ObjectPoolBase* pool) {
        poolList_.push_back(pool);
    }

    void remove(_ObjectPoolBase* pool) {
        poolList_.erase(std::remove(poolList_.begin(), poolList_.end(), pool));
    }

private:
    template <typename ObjectType, size_t Count>
    friend class _ObjectPool;
};

#define ObjectPoolManagerRef (*rpc::Singleton<rpc::_ObjectPoolManager>::instance())
#define ObjectPoolManagerSinglton rpc::Singleton<rpc::_ObjectPoolManager>

// object pool
// ObjectType object type
// Count pre-allocated count of object for each expand
template <typename ObjectType, size_t Count = 1>
class _ObjectPool : public _ObjectPoolBase {
    _ObjectPool(const _ObjectPool&) = delete;
    _ObjectPool(_ObjectPool&&) = delete;
    _ObjectPool<ObjectType>& operator=(const _ObjectPool&) = delete;

private:
    using FreeObjects = std::vector<ObjectType*>;
    using MemoryTrunks = std::list<char*>;
    FreeObjects freeObjects_; // free object list
    std::size_t freeCount_; // The count of object in free list
    MemoryTrunks memoryTrunks_; // occupied memory block list
    std::size_t allocatedCount_; // occupied object count
    std::size_t objectInHeap_; // The object count in heap

public:
    // ctor
    _ObjectPool() : freeCount_(0), allocatedCount_(0), objectInHeap_(0) {
        _newTrunk();
        ObjectPoolManagerRef.add(this);
    }

    // dtor
    virtual ~_ObjectPool() {
        for (auto trunk : memoryTrunks_) {
            free(trunk);
        }
    }

    // Create a instance of 'ObjectType'
    // @param args The arguments for ctor of ObjectType
    // @return The instance pointer of ObjectType
    template <typename...Args>
    ObjectType* createObject(Args...args) {
#if !USE_POOL
        allocatedCount_ += 1;
        objectInHeap_ += 1;
        return new ObjectType(std::forward<Args>(args)...);
#else
        ObjectType* memoryPtr = nullptr;
        allocatedCount_ += 1;
        if (!freeCount_) {
            if (!_newTrunk()) {
                return nullptr;
            }
        }
        auto index = freeCount_ - 1;
        memoryPtr = freeObjects_[index];
        freeCount_ = index;
        auto objectPtr = new(memoryPtr)ObjectType(std::forward<Args>(args)...);
        return objectPtr;
#endif // !USE_POOL
    }

    // Recycling
    // @param objectPtr The pointer of 'ObjectType'
    void disposeObject(ObjectType* objectPtr) {
#if !USE_POOL
        objectInHeap_ -= 1;
        allocatedCount_ -= 1;
        delete objectPtr;
#else
        allocatedCount_ -= 1;
        objectPtr->~ObjectType();
        if (freeCount_ >= freeObjects_.size()) {
            throw std::runtime_error("Try to free object in pool but never allocated!");
            return;
        }
        freeObjects_[freeCount_] = objectPtr;
        freeCount_ += 1;
#endif // !USE_POOL
    }

    // Returns the allocated object's count
    // @return The allocated object's count
    virtual size_t getCount() const override {
        return objectInHeap_;
    }

    // Returns std::type_info of 'ObjectType'
    // @return std::type_info of 'ObjectType'
    virtual const std::type_info& getTypeInfo() const override {
        return typeid(ObjectType);
    }

    // Returns the size in byte of 'ObjectType'
    virtual std::size_t getObjectSize() const override {
        return sizeof(ObjectType);
    }

private:
    // Pre-allocates more objects
    bool _newTrunk() {
        auto memory = (char*)malloc(Count * sizeof(ObjectType));
        if (!memory) {
            return false;
        }
        if (!freeObjects_.size()) {
            freeObjects_.resize(Count);
        } else {
            freeObjects_.resize(freeObjects_.size() + Count);
        }
        for (std::size_t i = 0; i < Count; i++) {
            freeObjects_[i] = reinterpret_cast<ObjectType*>(memory + i * sizeof(ObjectType));
        }
        objectInHeap_ += Count;
        memoryTrunks_.push_back(memory);
        freeCount_ += Count;
        return true;
    }
};

template <typename ObjectType>
static inline void DestroyObjectPool() {
    rpc::Singleton<rpc::_ObjectPool<ObjectType>>::destroy();
}

template <typename ObjectType>
static inline rpc::_ObjectPool<ObjectType>* getObjectPoolPtr() {
    return rpc::Singleton<rpc::_ObjectPool<ObjectType>>::instance();
}

template <typename ObjectType>
static inline rpc::_ObjectPool<ObjectType>& getObjectPoolRef() {
    return *(rpc::Singleton<rpc::_ObjectPool<ObjectType>>::instance());
}

// Returns a object from pool and constructs it
// ObjectType The prototype of object
// Args The argument of object's constructor
// @return The new created object's pointer
template <typename ObjectType, typename...Args>
static inline ObjectType* allocate(Args...args) {
    return getObjectPoolRef<ObjectType>().createObject(std::forward<Args>(args)...);
}

// Returns a object from pool and constructs it
// ObjectType The prototype of object
// Args The argument of object's constructor
// @return The new created object's reference
template <typename ObjectType, typename...Args>
static inline ObjectType& allocateRef(Args...args) {
    auto object = getObjectPoolRef<ObjectType>().createObject(std::forward<Args>(args)...);
    if (!object) {
        throw std::runtime_error("out of memory");
    }
    return *object;
}

// Recycles to object pool
// @param objectPtr Object pointer
template <typename ObjectType>
static inline void dispose(ObjectType* objectPtr) {
    getObjectPoolRef<ObjectType>().disposeObject(objectPtr);
}

// Recycles to object pool
// @param objectPtr Object reference
template <typename ObjectType>
static inline void dispose(ObjectType& objectRef) {
    dispose(&objectRef);
}

// Destructor for smart pointer
template <typename T>
class Deleter {
public:
    void operator()(T* obj) {
        if (!obj) {
            return;
        }
        dispose(dynamic_cast<T*>(obj));
    }
};

template <typename T>
void deleter(T* obj) {
#if USE_POOL
    if (!obj) {
        return;
    }
    auto* typeObject = dynamic_cast<T*>(obj);
    dispose(typeObject);
#endif // USE_POOL
}

template <typename T> using unique_pool_ptr = std::unique_ptr<T, Deleter<T>>;

template <typename T, typename...ARGS>
std::shared_ptr<T> make_shared_ptr(ARGS...args) {
    #if USE_POOL
    return std::shared_ptr<T>(allocate<T>(std::forward<ARGS>(args)...), deleter<T>);
    #else
    return std::shared_ptr<T>(new T(std::forward<ARGS>(args)...));
    #endif
}

}

using ObjectPoolManager = rpc::_ObjectPoolManager;

#define ObjectPoolSingleton(T) rpc::Singleton<rpc::_ObjectPool<T>>
